Skip to content

feat: add oneclaw module for 1Claw MCP integration#857

Open
kmjones1979 wants to merge 4 commits into
coder:mainfrom
kmjones1979:add-kmjones1979-oneclaw-module
Open

feat: add oneclaw module for 1Claw MCP integration#857
kmjones1979 wants to merge 4 commits into
coder:mainfrom
kmjones1979:add-kmjones1979-oneclaw-module

Conversation

@kmjones1979

Copy link
Copy Markdown

Summary

Resubmission of #845 (closed for structural reasons) with the reviewer feedback addressed and additional security hardening of the bootstrap flow.

Adds the kmjones1979 namespace and the oneclaw module, which provides vault-backed secrets and MCP server wiring for AI coding agents (Cursor, Claude Code) in Coder workspaces.

Changes since #845

Structural (addresses @DevelopmentCats review)

The reviewer asked that the module follow the standard schema used in the coder/ namespace:

Generally in almost all cases you would just have: main.tf, README.md, main.test.ts, main.tftest.hcl and script files which amount to maybe one or two scripts.

Done:

  • variables.tf and outputs.tf are consolidated into main.tf.
  • scripts/bootstrap.sh and scripts/setup.sh are merged into a single scripts/run.sh executed by one coder_script.
  • The Terraform-native provisioning mode (scripts/provision.sh, null_resource.provision, master_api_key) is removed. That mode relied on a local-exec provisioner writing a state file to the provisioner's cwd, which is ephemeral inside Coder template provisioners and cannot round-trip credentials into coder_env. Two modes remain: bootstrap (recommended) and manual.

Final tree:

registry/kmjones1979/
├── README.md
├── .images/avatar.png
└── modules/oneclaw/
    ├── README.md
    ├── main.tf
    ├── main.test.ts
    ├── main.tftest.hcl
    └── scripts/run.sh

Security hardening for the 1ck_ human bootstrap key

The 1ck_ human API key is privileged (can create and destroy vaults in the caller's 1Claw account), so the module goes out of its way to make sure it cannot be recovered from the workspace after bootstrap:

  1. The key is delivered to the script as a sensitive coder_env variable (_ONECLAW_HUMAN_API_KEY) rather than via templatefile() substitution. As a result, the literal key never appears in the rendered script body that lives in Terraform state or in the Coder agent's /tmp/coder-agent.log. The rendered script only shows HUMAN_KEY="\${_ONECLAW_HUMAN_API_KEY:-}".
  2. The key is sent to the 1Claw auth endpoint via curl --data-binary @- from stdin, so it never appears in ps aux / /proc/<pid>/cmdline.
  3. HUMAN_KEY and _ONECLAW_HUMAN_API_KEY are unset immediately after authentication, so downstream subprocesses spawned by the script do not inherit the key.
  4. Only the scoped ocv_ agent key and the vault id are persisted to ~/.1claw/bootstrap.json and the MCP config files.
  5. README.md documents a post-bootstrap cleanup flow: once the state file exists, the user is instructed to set human_api_key = "" in their Terraform so subsequent restarts do not reference the human key at all.

Test plan

Verified against a local Coder server (v2.31.9) running the module with real 1Claw credentials.

  • terraform test passes (4 runs, Terraform 1.14 via Docker)
  • bun test main.test.ts passes (5 tests, including an explicit assertion that the human key value is not embedded in the rendered script)
  • shellcheck is clean on scripts/run.sh
  • bun run fmt leaves the tree unchanged
  • Live workspace on local Coder: first boot creates vault + agent + policy and writes bootstrap.json + Cursor/Claude MCP configs
  • Live workspace restart is idempotent — the script detects the state file and skips provisioning
  • Post-bootstrap cleanup flow (human_api_key = "", coder update, restart) continues to work using cached credentials
  • Filesystem audit after each scenario confirms the 1ck_ key value does not appear in any file on the workspace (state file, MCP configs, agent log, script log, shell init files, /proc/<pid>/environ of any Coder process)

Made with Cursor

Add kmjones1979 namespace and oneclaw module, ported from
1clawAI/1claw-coder-workspace-module. Provides vault-backed secrets
and MCP server config for AI coding agents in Coder workspaces.

- Namespace: kmjones1979 (avatar from GitHub)
- Module: oneclaw with three provisioning modes (terraform-native,
  shell bootstrap, manual)
- Tests: main.tftest.hcl (5 runs) and main.test.ts (5 tests)
- Scripts: provision.sh, bootstrap.sh, setup.sh

Made-with: Cursor
…dling

Addresses reviewer feedback on closed PR coder#845 that the module was "split up
way more than usual" and did not follow the registry module schema.

Structure (matches the coder/ namespace conventions):
- Collapse variables.tf + outputs.tf into main.tf
- Merge scripts/bootstrap.sh + scripts/setup.sh into a single scripts/run.sh
  executed by a single coder_script
- Remove Terraform-native provisioning mode (scripts/provision.sh,
  null_resource.provision, master_api_key): it relied on local-exec writing a
  state file to the provisioner's cwd, which is ephemeral inside Coder template
  provisioners and therefore cannot round-trip credentials into coder_env
- Keep two supported modes: bootstrap (human 1ck_ key, recommended) and
  manual (pre-provisioned scoped ocv_ key)

Security hardening for the 1ck_ human bootstrap key:
- Deliver the key via a sensitive coder_env (_ONECLAW_HUMAN_API_KEY) instead
  of templatefile() substitution, so the literal key never appears in the
  rendered script body stored in Terraform state or logged to the workspace's
  /tmp/coder-agent.log
- Send the key to the 1Claw auth endpoint via curl --data-binary @- from stdin
  so it does not appear in process argv (ps/proc/cmdline)
- Unset HUMAN_KEY and _ONECLAW_HUMAN_API_KEY as soon as auth completes so
  downstream processes do not inherit the key
- Only the scoped ocv_ agent key and vault id are persisted to
  ~/.1claw/bootstrap.json and the MCP config files
- README documents post-bootstrap cleanup (set human_api_key = "" once the
  state file exists) and the full security guarantees

Tested end-to-end against a local Coder server with real 1Claw credentials:
first boot, idempotent restart, and post-bootstrap cleanup all succeed and
leave no copy of the 1ck_ value anywhere on the workspace filesystem or in
its process environments.

Made-with: Cursor

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new community namespace (kmjones1979) and a new Terraform module (oneclaw) to configure 1Claw vault-backed secrets access and write/merge MCP server configuration for AI agents in Coder workspaces, including an optional “bootstrap” flow that provisions 1Claw resources from inside the workspace.

Changes:

  • Introduces registry/kmjones1979/ contributor namespace metadata (README + avatar reference).
  • Adds registry/kmjones1979/modules/oneclaw/ with Terraform module wiring (coder_env + coder_script) and a unified scripts/run.sh.
  • Adds Terraform (main.tftest.hcl) and Bun (main.test.ts) tests covering manual vs bootstrap behavior and ensuring the human key is not embedded in the rendered script.

Reviewed changes

Copilot reviewed 6 out of 8 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
registry/kmjones1979/README.md Adds namespace/frontmatter metadata for the new community contributor.
registry/kmjones1979/modules/oneclaw/README.md Documents module usage (bootstrap/manual) and the security model/cleanup steps.
registry/kmjones1979/modules/oneclaw/main.tf Implements the module’s inputs/outputs and Coder resources for env injection and startup script execution.
registry/kmjones1979/modules/oneclaw/main.tftest.hcl Adds Terraform plan-based assertions for the module’s two operating modes and env/script wiring.
registry/kmjones1979/modules/oneclaw/main.test.ts Adds integration-style tests validating rendered script behavior (including no human key literal).
registry/kmjones1979/modules/oneclaw/scripts/run.sh Implements the bootstrap provisioning flow plus MCP config write/merge logic.

VAULT_NAME_IN="${VAULT_NAME}"
AGENT_NAME_IN="${AGENT_NAME}"
POLICY_PATH_IN="${POLICY_PATH}"
STATE_DIR=$(eval echo "${STATE_DIR}")

write_mcp_config() {
local target_path="$1" label="$2" tmp_file="$3"
target_path=$(eval echo "$target_path")
Comment on lines +108 to +114
vault=$(echo "$list_response" | python3 -c "
import json, sys
for v in json.load(sys.stdin).get('vaults', []):
if v['name'] == '$VAULT_NAME_IN':
print(v['id']); sys.exit(0)
sys.exit(1)
") || die "Could not find existing vault named '$VAULT_NAME_IN'"
Comment on lines +214 to +216
log "WARNING: No API token or vault ID available — skipping MCP config"
log "Provide api_token + vault_id, or set human_api_key/master_api_key"
exit 0
Comment on lines +118 to +122
variable "order" {
type = number
description = "The order determines the position of app in the UI presentation."
default = null
}

data "coder_workspace" "me" {}

data "coder_workspace_owner" "me" {}
Comment on lines +83 to +86
# Key is no longer needed; scrub from process memory before any other work.
HUMAN_KEY=""
unset HUMAN_KEY

main.tf:
- Remove unused `order` variable. coder_script does not support an order
  field, so the variable was dead code.
- Remove unused `data "coder_workspace_owner" "me"` source.

scripts/run.sh:
- Replace `eval echo` (used to expand `$HOME` / `~` in user-overridable
  config paths) with a dedicated expand_path() helper that uses bash
  string substitution and a case statement. Adds unit-test coverage
  confirming that injected `$(...)` and backticks are not executed.
- Rewrite the existing-vault lookup so the vault name is passed to
  Python via argv instead of being interpolated into the inline Python
  source. The JSON payload is now fed to Python on stdin via a
  here-string instead of via a pipe + here-doc combination, which
  shellcheck flagged (SC2259) as broken (the heredoc would have
  overridden the piped stdin and the program would have failed to
  parse the response).
- `unset _ONECLAW_HUMAN_API_KEY` immediately after reading it into
  HUMAN_KEY at the top of the script, so the privileged bootstrap
  key is no longer visible in /proc/<pid>/environ for the lifetime
  of the script run.
- Update the "no API token or vault ID" warning to drop the mention
  of `master_api_key`, which was removed earlier in this PR.

Made-with: Cursor
@kmjones1979

Copy link
Copy Markdown
Author

Thanks for the careful review — addressed all seven items in 4d5b414.

Copilot comment File Fix
eval echo on STATE_DIR enables command substitution scripts/run.sh:17 Replaced with expand_path() helper that does pure bash string substitution (${p//\$HOME/$HOME} and a case for ~/~/...). Unit-tested locally with '/tmp/$(touch …).cfg' and backtick payloads to confirm nothing executes.
eval echo on user-provided config paths scripts/run.sh:164 Same expand_path() helper.
Vault name interpolated into inline Python scripts/run.sh:108-114 Vault name is now passed via argv. While I was there I also fixed an unrelated bug shellcheck caught (SC2259): the rewrite originally used python3 - "$NAME" <<'PYEOF' which would have overridden the piped JSON with the heredoc; the final form uses python3 -c '...' "$NAME" <<<"$list_response" so argv carries the name, stdin carries the JSON, and the Python source is never interpolated.
Stale master_api_key reference in warning scripts/run.sh:214-216 Updated to "Provide api_token + vault_id (manual mode), or set human_api_key (bootstrap mode)" since the master-key path was removed earlier in this PR.
order variable declared but unused main.tf:118-122 Removed. coder_script doesn't expose an order field, so it was dead surface.
data "coder_workspace_owner" "me" unused main.tf:126 Removed.
_ONECLAW_HUMAN_API_KEY lingered in /proc/<pid>/environ for the whole run scripts/run.sh:83-86 Moved unset _ONECLAW_HUMAN_API_KEY to immediately after HUMAN_KEY="${_ONECLAW_HUMAN_API_KEY:-}" at the top of the script (line 39 now). The shell variable HUMAN_KEY is still scrubbed after auth completes, and the belt-and-suspenders unset of HUMAN_KEY after the bootstrap call is kept for the state-file short-circuit path.

All checks still green: terraform test (4 runs), bun test (5 tests), shellcheck, bun run fmt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants